﻿#region Class Header
/* ========================================================================
* 【本类功能概述】
* 
* 作者：chinesedragon 
* 时间：5/4/2013 4:25:07 PM
* 文件名：DownloadHelper
* 版本：V1.0.1
*
* 修改时间： 
* 修改说明：
* ========================================================================
*/
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;

namespace Updater
{
    #region DownloadHelper Factory

    class DownloadHelperFactory
    {
        private enum URLType
        {
            UNKOWN,
            HTTP,
            FTP,
            WIN32_SHARE,
        }

        private static URLType GetUrlType(string url)
        {
            string strUrl = url.ToLower();
            if (strUrl.StartsWith("http://"))
                return URLType.HTTP;
            if (strUrl.StartsWith("ftp://"))
                return URLType.FTP;
            if (strUrl.StartsWith("\\\\"))
                return URLType.WIN32_SHARE;
            return URLType.UNKOWN;
        }

        public static DownloadHelper CreateInstance(string remoteUrl, string user, string pwd)
        {
            URLType urlType = GetUrlType(remoteUrl);
            switch (urlType)
            {
                case URLType.HTTP:
                    return new DownloadHelperForHttp();
                case URLType.FTP:
                    return new DownloadHelperForFtp(user, pwd);
                case URLType.WIN32_SHARE:
                    return new DownloadHelperForWin32Share(remoteUrl, user, pwd);
                default:
                    return null;
            }
        }
    }

    #endregion


    abstract class DownloadHelper : IDisposable
    {
        public abstract void DownloadFile(string localPath, string remoteUrl, string fileName,
            ref long downloadSize, BackgroundWorker worker = null, long totalSize = 0);

        public abstract void Dispose();
    }


    #region DownloadHelper For Http

    class DownloadHelperForHttp : DownloadHelper
    {
        public DownloadHelperForHttp()
        {

        }

        public override void DownloadFile(string localPath, string remoteUrl, string fileName, ref long downloadSize, BackgroundWorker worker = null, long totalSize = 0)
        {
            string url = remoteUrl + "/" + fileName;
            url = url.Replace("\\", "/");
            string path = localPath + "\\" + fileName;
            path = path.Replace("\\\\", "\\");
            DownloadFile(url, path, worker, totalSize, ref downloadSize);
        }

        public override void Dispose()
        {
            ;
        }

        private void DownloadFile(string url, string path, BackgroundWorker worker, long totalSize, ref long downloadSize)
        {
            string dir = Path.GetDirectoryName(path);
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);

            WebRequest req = WebRequest.Create(url);
            WebResponse res = req.GetResponse();

            if (res.ContentLength == 0)
                return;

            long fileLength = res.ContentLength;
            using (Stream stream = res.GetResponseStream())
            {
                byte[] buffer = new byte[fileLength];
                int allByte = buffer.Length;
                int startByte = 0;
                while (fileLength > 0)
                {
                    int downByte = stream.Read(buffer, startByte, allByte);
                    if (downByte == 0)
                        break;
                    startByte += downByte;
                    allByte -= downByte;

                    downloadSize += downByte;
                    if (worker != null)
                    {
                        var progress = Math.Min(downloadSize, totalSize);
                        var percentage = (int)((Math.Round(Convert.ToDecimal(progress) / Convert.ToDecimal(totalSize) * 1.0M, 2)) * 100);
                        worker.ReportProgress(percentage);
                    }  
                }

                using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write))
                {
                    fs.Write(buffer, 0, buffer.Length);
                }
            }
        }
    }

    #endregion

    #region DownloadHelper For Ftp

    class DownloadHelperForFtp : DownloadHelper
    {
        private readonly string userName = string.Empty;
        private readonly string password = string.Empty;

        public DownloadHelperForFtp(string user, string pwd)
        {
            userName = user;
            password = pwd;
        }

        public override void DownloadFile(string localPath, string remoteUrl, string fileName, ref long downloadSize, BackgroundWorker worker = null, long totalSize = 0)
        {
            string url = remoteUrl + "/" + fileName;
            url = url.Replace("//", "/").Replace("ftp:/", "ftp://");
            string path = localPath + "\\" + fileName;
            path = path.Replace("\\\\", "\\");
            DownloadFile(url, path, worker, totalSize, ref downloadSize);
        }

        public override void Dispose()
        {
            ;
        }

        private void DownloadFile(string url, string path, BackgroundWorker worker, long totalSize, ref long downloadSize)
        {
            string dir = Path.GetDirectoryName(path);
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);

            FtpWebRequest ftpReq = WebRequest.Create(url) as FtpWebRequest;
            ftpReq.Credentials = new NetworkCredential(userName, password);
            ftpReq.Method = WebRequestMethods.Ftp.DownloadFile;
            FtpWebResponse res = ftpReq.GetResponse() as FtpWebResponse;

            if (res.ContentLength == 0)
                return;

            long fileLength = res.ContentLength;
            using (Stream stream = res.GetResponseStream())
            {
                byte[] buffer = new byte[fileLength];
                int allByte = buffer.Length;
                int startByte = 0;
                while (fileLength > 0)
                {
                    int downByte = stream.Read(buffer, startByte, allByte);
                    if (downByte == 0)
                        break;
                    startByte += downByte;
                    allByte -= downByte;

                    downloadSize += downByte;
                    if (worker != null)
                    {
                        var progress = Math.Min(downloadSize, totalSize);
                        var percentage = (int)((Math.Round(Convert.ToDecimal(progress) / Convert.ToDecimal(totalSize) * 1.0M, 2)) * 100);
                        worker.ReportProgress(percentage);
                    }                    
                }

                using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write))
                {
                    fs.Write(buffer, 0, buffer.Length);
                }
            }
        }
    }

    #endregion

    #region Windows Share DownloadHelper

    class DownloadHelperForWin32Share : DownloadHelper
    {
        internal class Win32Share : IDisposable
        {
            [DllImport("advapi32.dll", SetLastError = true)]
            static extern bool LogonUser(string username, string domain, string password,
            int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
            [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
            static extern bool CloseHandle(IntPtr handle);
            [DllImport("advapi32.dll")]
            static extern bool ImpersonateLoggedOnUser(IntPtr hToken);
            [DllImport("advapi32.dll")]
            static extern bool RevertToSelf();

            const int LOGON32_PROVIDER_DEFAULT = 0;
            const int LOGON32_LOGON_NEWCREDENTIALS = 9;
            private bool bDisposed = false;

            public Win32Share(string domain, string username, string password)
            {
                IntPtr pExistingTokenHandle = new IntPtr(0);
                IntPtr pDuplicateTokenHandle = new IntPtr(0);

                try
                {
                    // get handle to token
                    bool bImpresonated = LogonUser(username, domain, password,
                        LOGON32_LOGON_NEWCREDENTIALS, LOGON32_PROVIDER_DEFAULT, ref pExistingTokenHandle);
                    if (bImpresonated)
                    {
                        if (!ImpersonateLoggedOnUser(pExistingTokenHandle))
                        {
                            int nErrorCode = Marshal.GetLastWin32Error();
                            throw new Exception("ImpersonateLoggedOnUser error;Code=" + nErrorCode);
                        }
                    }
                    else
                    {
                        int nErrorCode = Marshal.GetLastWin32Error();
                        throw new Exception("LogonUser error;Code=" + nErrorCode);
                    }
                }
                finally
                {
                    //close handle(s)
                    if (pExistingTokenHandle != IntPtr.Zero)
                        CloseHandle(pExistingTokenHandle);
                    if (pDuplicateTokenHandle != IntPtr.Zero)
                        CloseHandle(pDuplicateTokenHandle);
                }
            }

            protected virtual void Dispose(bool disposing)
            {
                if (!bDisposed)
                {
                    RevertToSelf();
                    bDisposed = true;
                }
            }

            public void Dispose()
            {
                Dispose(true);
            }
        }

        private readonly Win32Share w32Share = null;

        public DownloadHelperForWin32Share(string domain, string user, string pwd)
        {
            w32Share = new Win32Share(domain, user, pwd);
        }

        public override void DownloadFile(string localPath, string remoteUrl, string fileName, ref long downloadSize, BackgroundWorker worker = null, long totalSize = 0)
        {
            string url = remoteUrl + "/" + fileName;
            string path = localPath + "\\" + fileName;
            DownloadFile(url, path, worker, totalSize, ref downloadSize);
        }

        private void DownloadFile(string url, string path, BackgroundWorker worker, long totalSize, ref long downloadSize)
        {
            string dir = Path.GetDirectoryName(path);
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);

            File.Copy(url, path, true);
            FileInfo fi = new FileInfo(path);
            downloadSize += fi.Length;
            if (worker != null)
            {
                var progress = Math.Min(downloadSize, totalSize);
                var percentage = (int)((Math.Round(Convert.ToDecimal(progress) / Convert.ToDecimal(totalSize) * 1.0M, 2)) * 100);
                worker.ReportProgress(percentage);
            }
        }

        public override void Dispose()
        {
            if (w32Share != null)
                w32Share.Dispose();
        }
    }

    #endregion
}
